package com.abel.bigwater.tlv.model

import com.alibaba.fastjson.annotation.JSONField
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.collections.ArrayList
import kotlin.experimental.and

/**
 * 字段名	字段类型	描述和要求
帧起始标志	BYTE	固定为68H
通讯模块IMEI	BYTE[15]	ASCII
协议版本号	BYTE	这部分AES加密
指令	BYTE
数据长度	BYTE[2]
TLV数据	BYTE[n]
TLV数据	BYTE[n]
...
校验码	BYTE	从帧起始符开始到校验码之前的所有各字节进行二进制算术累加，不计超过FFH的溢出值
帧结束标志	BYTE	固定为16H
数据默认为从高位到低位
 */
data class TlvDevPacket(
        var imei: String? = null,
        var checksum: Short = 0) : TlvDataApi {
    val head: Byte = 0x68

    var body: TlvDevBody? = null

    val tail: Byte = 0x16

    @JSONField(serializeUsing = TlvExceptionSerializer::class)
    var parseResult: TlvException? = null

    private val log = LoggerFactory.getLogger(TlvDevPacket::class.java)

    /** 0 - body */
    companion object {
        val MIN_LEN = 1 + 15 + (0) + 1 + 1
    }

    override fun finalLen(): Int {
        val len = body?.finalLen() ?: throw TlvInvalidException("body can't be null")
        return MIN_LEN + len
    }

    /**
     * buf may be not consumed.
     */
    override fun fill(buf: ByteBuf): ByteBuf {
        if (imei?.length != 15) throw TlvInvalidException("invalid imei: ${imei?.length}")
        if (body == null) throw TlvInvalidException("body can't be null")

        // 加密
        var result: ByteArray? = null
        with(buf.alloc().buffer()) {
            body!!.fill(this)

            // encrypt
            val ba = ByteArray(readableBytes())
            readBytes(ba)
            result = TlvHelper.encryptAes(ba)
        }

        // buf may not be consumed, so write to a new buffer firstly
        buf.writeByte(head.toInt())
        buf.writeBytes(imei?.toByteArray(Charsets.US_ASCII))
        // fill to buf in whole.
        buf.writeBytes(result!!)

        // checksum
        with(buf.duplicate()) {
            val temp = ByteArray(readableBytes())
            readBytes(temp)

            checksum = 0
            temp.forEach { checksum = (checksum + it.toShort()).toShort() and 0xFF }
        }

        buf.writeByte(checksum.toInt())
        buf.writeByte(tail.toInt())

        return buf
    }

    override fun parse(buf: ByteBuf): ByteBuf {
        if (buf.readableBytes() < MIN_LEN + TlvDevBody.MIN_LEN)
            throw TlvNotEnoughByteException("not enough bytes for dev packet: ${buf.readableBytes()}")

        val ba = ByteArray(buf.readableBytes())
        buf.readBytes(ba)
        if (ba.first() != head || ba.last() != tail) {
            throw TlvInvalidException("BAD head or tail: ${ba.first()}-${ba.last()}")
        }

        imei = String(ba.copyOfRange(1, 16), Charsets.US_ASCII)
        checksum = ba[ba.size - 2].toShort() and 0xFF
        var cs2: Short = 0
        for (b1 in ba.copyOfRange(0, ba.size - 2)) {
            cs2 = (cs2 + b1).toShort() and 0xFF
        }
        log.warn("Checksum comparing: ${cs2} (computed) == ${checksum} (pdu) ?")

        // decrpt by AES/ECB/NoPadding
        val result = TlvHelper.decryptAes(ba.copyOfRange(16, ba.size - 2))
        log.warn("Plain text: ${TlvHelper.toHexString(result)}")

        if (result.size < TlvDevBody.MIN_LEN) throw TlvInvalidException("Bytes are not enough: ${result.size}")
        with(buf.alloc().buffer().writeBytes(result)) {
            // body
            try {
                body = TlvDevBody(dataList = ArrayList())
                body?.parse(this)
            } catch (ex: TlvException) {
                parseResult = ex
            }
            log.error("解析设备消息结果: ${parseResult?.message}", parseResult)
        }

        return buf
    }

}